package org.openmore.platform.pay;

import cn.binarywang.wx.miniapp.config.impl.WxMaRedisConfigImpl;
import com.alibaba.fastjson.JSON;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import com.thoughtworks.xstream.XStream;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.openmore.common.enums.PaymentType;
import org.openmore.common.exception.OpenmoreException;
import org.openmore.common.sign.MD5Tools;
import org.openmore.platform.dto.common.BaseResponse;
import org.openmore.platform.entity.PlatformConfig;
import org.openmore.platform.entity.enums.PlatformType;
import org.openmore.platform.service.PlatformConfigService;
import org.openmore.platform.utils.wechatUtils.HttpRequestUtil;
import org.openmore.platform.utils.wechatUtils.MapEntryConverter;
import org.openmore.platform.utils.wechatUtils.WechatPublicSignUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;

import java.io.UnsupportedEncodingException;
import java.util.*;

@Service
public class WechatPayService implements PaymentService {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private static Map<String, WxPayService> wxPayServiceMap;

    @Autowired
    private HttpRequestUtil request;

    @Autowired
    private PlatformConfigService platformConfigService;

    /**
     * 单例获得所有的微信配置
     *
     * @return
     */
    public static Map<String, WxPayService> getInstance() {
        if (wxPayServiceMap == null) {
            wxPayServiceMap = new HashMap<>();
        }
        return wxPayServiceMap;
    }

    public WxPayService getWxPayService(String tenantId, PaymentType paymentType) {
        if (StringUtils.isEmpty(tenantId)) {
            throw new OpenmoreException("租户id不能为空");
        }

        Map<String, WxPayService> serviceMap = getInstance();
        WxPayService wxService = serviceMap.get(paymentType+tenantId);
        if (wxService == null) {
//          没有微信服务，则根据tenantid获得对应的微信配置
            Example example = new Example(PlatformConfig.class);
            Example.Criteria criteria = example.createCriteria().andEqualTo("deleted", false)
                    .andEqualTo("platformType", PlatformType.WECHAT_PAY);
            //多租户模型租户不为空则匹配对应租户配置
            if(!StringUtils.isEmpty(tenantId)){
                criteria.andEqualTo("tenantId", tenantId);
            }
            List<PlatformConfig> platformConfigs = platformConfigService.selectAll(tenantId, PlatformType.WECHAT_PAY, null, null);
            PlatformConfig wxConfig = platformConfigs.get(0);
            if (wxConfig == null) {
                throw new OpenmoreException("找不到应用的微信商户平台配置信息");
            }
            String appId = null;
            //JSAPI--JSAPI支付（或小程序支付）、NATIVE--Native支付、APP--app支付，MWEB--H5支付，不同trade_type决定了调起支付的方式，请根据支付产品正确上传
            //MICROPAY--付款码支付，付款码支付有单独的支付接口，所以接口不需要上传，该字段在对账单中会出现
            String tradeType = null;
            if(paymentType == PaymentType.WECHAT_MINIAPP){
                List<PlatformConfig> miniAppPlatformConfigs = platformConfigService.selectAll(tenantId, PlatformType.WECHAT_MINIAPP, null, null);
                if(CollectionUtils.isEmpty(miniAppPlatformConfigs)){
                    throw new OpenmoreException(400, "请先配置微信小程序支付账户");
                }
                appId = miniAppPlatformConfigs.get(0).getAppid();
                tradeType = "JSAPI";
            }else if(paymentType == PaymentType.WECHAT_H5){
                List<PlatformConfig> mpPlatformConfigs = platformConfigService.selectAll(tenantId, PlatformType.WECHAT_MP, null, null);
                if(CollectionUtils.isEmpty(mpPlatformConfigs)){
                    throw new OpenmoreException(400, "请先配置微信公众平台支付账户");
                }
                appId = mpPlatformConfigs.get(0).getAppid();
                tradeType = "JSAPI";
            }else if(paymentType == PaymentType.WECHAT){
                List<PlatformConfig> wechatPlatformConfigs = platformConfigService.selectAll(tenantId, PlatformType.WECHAT_OPEN, null, null);
                if(CollectionUtils.isEmpty(wechatPlatformConfigs)){
                    throw new OpenmoreException(400, "请先配置微信开放平台支付账户");
                }
                appId = wechatPlatformConfigs.get(0).getAppid();
                tradeType = "APP";
            }else if(paymentType == PaymentType.WECHAT_PAD){
                List<PlatformConfig> wechatPadPlatformConfigs = platformConfigService.selectAll(tenantId, PlatformType.WECHAT_OPEN_PAD, null, null);
                if(CollectionUtils.isEmpty(wechatPadPlatformConfigs)){
                    throw new OpenmoreException(400, "请先配置微信开放平台PAD端支付账户");
                }
                appId = wechatPadPlatformConfigs.get(0).getAppid();
                tradeType = "APP";
            }
            WxMaRedisConfigImpl configStorage = new WxMaRedisConfigImpl();
            WxPayConfig payConfig = new WxPayConfig();
            payConfig.setAppId(appId);
            payConfig.setMchId(wxConfig.getAppid());
            payConfig.setMchKey(wxConfig.getMchKey());
            payConfig.setKeyPath("");
            payConfig.setSignType("RSA2");
//          创建微信服务对象
            wxService = new WxPayServiceImpl();
            wxService.setConfig(payConfig);
            // 缓存起来
            serviceMap.put(paymentType+tenantId, wxService);
        }
        return wxService;
    }


    public BaseResponse payment(String host, String tenantId, Payment payment) throws Exception {
        if(StringUtils.isEmpty(tenantId)){
            throw new OpenmoreException(400, "应用ID不能为空");
        }
        if(null == payment || null == payment.getPayType()){
            throw new OpenmoreException(400, "支付类型不能为空");
        }
        WxPayConfig wxPayConfig = getWxPayService(tenantId, payment.getPayType()).getConfig();
        logger.error(JSON.toJSONString(payment));
        logger.error(JSON.toJSONString(wxPayConfig));
        logger.debug("==>openId:"+payment.getOpenid());
        String requestXML = this.getRquestXML(payment, wxPayConfig);
        logger.debug("==>request params:"+ requestXML);
        logger.error(requestXML);
        String url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        String result = request.sendPost(url, requestXML);
        logger.error("==>result:"+new String(result.getBytes("iso-8859-1"), "utf-8"));
        System.out.println("==>result:" + result);
        XStream magicApi = new XStream();
        magicApi.setupDefaultSecurity(magicApi);
        String timestamp = WechatPublicSignUtil.create_timestamp();
        magicApi.registerConverter(new MapEntryConverter());
        magicApi.alias("xml", Map.class);
        Map<String, String> map = (Map<String, String>) magicApi.fromXML(result);
        logger.debug("==>result:"+JSON.toJSONString(map));
        if(null ==map || !map.get("return_code").equals("SUCCESS")){
            throw new OpenmoreException(400, map.get("return_msg"));
        }
        Map<String, String> newMap = new HashMap<>();
//        MichaelTang add 18.06.06
        if (payment.getPayType() == PaymentType.WECHAT_MINIAPP) {
            if (!StringUtils.isEmpty(map.get("err_code"))) {
                throw new OpenmoreException(400, map.get("err_code_des"));
            }
            String reSignTemp = "appId=" + map.get("appid")
                    + "&nonceStr=" + map.get("nonce_str")
                    + "&package=prepay_id=" + map.get("prepay_id")
                    + "&signType=MD5"
                    + "&timeStamp=" + timestamp
                    + "&key=" + wxPayConfig.getMchKey();
            String reSign = MD5Tools.md5(reSignTemp).toUpperCase();
//            String reSign = MD5(reSignTemp).toUpperCase();
            newMap.put("timeStamp", timestamp);
            newMap.put("nonceStr", map.get("nonce_str"));
            newMap.put("package", "prepay_id=" + map.get("prepay_id"));
            newMap.put("signType", "MD5");
            newMap.put("paySign", reSign);
            newMap.put("prepayId", map.get("prepay_id"));

            // 保存起来，7天内可用
//            redisManager.set(payment.getOpenid(), map.get("prepay_id"), 7l, TimeUnit.DAYS);
        } else {

            String reSignTemp = "appid=" + map.get("appid")
                    + "&noncestr=" + map.get("nonce_str")
                    + "&package=Sign=WXPay"
                    + "&partnerid=1491611972"
                    + "&prepayid=" + map.get("prepay_id")
                    + "&timestamp=" + timestamp
                    + "&key=" + wxPayConfig.getMchKey();
            String reSign = MD5Tools.md5(reSignTemp).toUpperCase();
            newMap.put("appid", map.get("appid"));
            newMap.put("noncestr", map.get("nonce_str"));
            newMap.put("package", "Sign=WXPay");
            newMap.put("partnerId", "1491611972");
            newMap.put("prepayId", map.get("prepay_id"));
            newMap.put("timestamp", timestamp);
            newMap.put("reSign", reSign);
        }
        return BaseResponse.buildSuccessResponse(newMap);
    }

    private List<Map.Entry<String, String>> sortList(Payment payment, Object config) throws Exception {
        Map<String, String> configs = BeanUtils.describe(config);
        configs.remove("class");
        configs.remove("secret");
        Map<String, String> payments = BeanUtils.describe(payment);
        payments.remove("class");
        if (payment.getPayType() == PaymentType.WECHAT || payment.getPayType() == PaymentType.WECHAT_PAD) {
            payments.remove("openid");
        }
        payments.remove("payType");
        List<Map.Entry<String, String>> configList = new ArrayList<>(configs.entrySet());
        List<Map.Entry<String, String>> paymentList = new ArrayList<>(payments.entrySet());
        for (int i = 0; i < paymentList.size(); i++) {
            configList.add(paymentList.get(i));
        }
        Collections.sort(configList, new Comparator<Map.Entry<String, String>>() {
            public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
                return (o1.getKey()).toString().compareTo(o2.getKey());
            }
        });
        return configList;
    }

    private String getRequestParam(Payment payment, WxPayConfig config) throws Exception {
        List<Map.Entry<String, String>> sortList = this.sortList(payment, config);
        String tokenParam = "";
        for (int i = 0; i < sortList.size(); i++) {
            if (i == 0)
                tokenParam = tokenParam + sortList.get(0);
            else
                tokenParam = tokenParam + "&" + sortList.get(i);
        }
        String requestParam = tokenParam;
        String signTemp = tokenParam + "&key=" + config.getMchKey();
        String token = MD5Tools.md5(signTemp);
        requestParam = requestParam + "&sign=" + token;
        return requestParam;
    }

    private String getRquestXML(Payment payment, WxPayConfig config) throws Exception {
        String requestParam = this.getRequestParam(payment, config);
        logger.debug(requestParam);
        String params[] = requestParam.split("&");
        Map<String, String> map = new HashMap<>();
        StringBuilder sb = new StringBuilder();
        sb.append("<xml>\n");
        for (int i = 0; i < params.length; i++) {
            String mapParams[] = params[i].split("=");
            sb.append("<" + mapParams[0] + ">");
            sb.append(mapParams[1]);
            sb.append("</" + mapParams[0] + ">\n");
        }
        sb.append("</xml>");
        String requestXML = new String(sb.toString().getBytes(), "utf-8");
        return requestXML;
    }

    public WxPayRefundResult refound(String tenantId, PaymentType paymentType, WxPayRefundRequest refundRequest) throws WxPayException {
        return getWxPayService(tenantId, paymentType).refund(refundRequest);
    }

    /**
     * 校验签名
     */
    public String getSign(Map<String, Object> map, PaymentType payType, String tenantId) throws UnsupportedEncodingException {
        ArrayList<String> list = new ArrayList<String>();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            //sign不参与验签
            //参数为空不参与签名
            if (entry.getValue() != "" && !"sign".equals(entry.getKey())) {
                list.add(entry.getKey() + "=" + entry.getValue());
            }
        }
        int size = list.size();
        String[] arrayToSort = list.toArray(new String[size]);
        Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < size; i++) {
            sb.append(arrayToSort[i]);
            if (i != size - 1) {
                sb.append("&");
            }
        }
        String result = sb.toString();
        WxPayService wxPayService = getWxPayService(tenantId, payType);
        result += "&key=" + wxPayService.getConfig().getMchKey();
        result = MD5Tools.md5(result);
        return result;
    }
}
